/**
 * @file ring_buffer.c
 * @author Dustin Balise
 * @date 8/25/11
 * 
 * @brief The <em>Ring Buffer</em> API is an implementation of a ring buffer targeted for small embedded systems.
 *
 * @section ring_buffer_sec Ring Buffer API
 * The <em>Ring Buffer</em> API is designed with the hope of being a portable ring buffer implementation
 * for embedded systems. It is not meant to compete with more elegant APIs that are designed to make use of more 
 * advanced processors and operating systems. The API provides the following features:
 *  - Data appending to a ring buffer.
 *  - Data removal from a ring buffer.
 *  - Data peeking from a ring buffer.
 *
 * The API functions take care of all implementation details. Direct access to a <em>Ring Buffer</em>
 * structure instance is both unnecessary and will most likely cause an error. The implementation of the ring 
 * buffer makes use of read and write counts to determine the amount of data available in the buffer. The read and write
 * counts are both signed integers that are allowed to wrap over. The fill count of the buffer can always be calculated 
 * by the equation \f$Fill\ Count = Write\ Count - Read\ Count\f$.
 *
 * In the interest of portability the API requires the user to supply their own function to copy data from the desired
 * source to a destination buffer. The API then uses this function to properly append data to the ring buffer. The
 * user may also elect to provide functions for resetting the data source and making the API interrupt or thread safe
 * as desired.
 *
 * @section License
 *
 * Copyright © 2011 Dustin R. Balise
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 
 * Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
#include "ring_buffer.h"


/*
 *  Constants.
 */
// Intentionally left blank.


/*
 *  Types and structures.
 */
// Intentionally left blank.


/*
 *  Private functions.
 */
static size_t ring_buffer_get_free_size(const ring_buffer_t * p_ring);
static size_t ring_buffer_get_fill_count(const ring_buffer_t * p_ring);
static void ring_buffer_update_write_count(ring_buffer_t * p_ring, size_t size);
static void ring_buffer_update_read_count(ring_buffer_t * p_ring, size_t size);
static void ring_buffer_update_start(ring_buffer_t * p_ring, size_t size);
static uint8_t * ring_buffer_get_end(const ring_buffer_t * p_ring);
static bool ring_buffer_copy(const ring_buffer_t * p_ring,
                             size_t offset,
                             void * p_destination,
                             size_t size,
                             size_t * p_size_copied);
static void ring_buffer_enter_critical(const ring_buffer_t * p_ring);
static void ring_buffer_exit_critical(const ring_buffer_t * p_ring);


/**
 * Initializes the ring buffer setting structure to the default values.
 *
 * @param[out] p_init A pointer to the ring buffer settings.
 */
void ring_buffer_defaults(ring_buffer_init_t * p_init)
{
    p_init->p_buffer = NULL;
    p_init->buffer_size = 0;
    p_init->element_size = sizeof(uint8_t);
    p_init->append = NULL;
    p_init->reset_data_source = NULL;
    p_init->enter_critical = NULL;
    p_init->exit_critical = NULL;
}

/**
 * Initializes a ring buffer instance.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 * 
 * @param[in] p_buffer A pointer to the ring buffer.
 *
 * @param[in] buffer_size The size of the ring buffer in bytes.
 *
 * @param[in] append A pointer to a function that fills the ring buffer with data. 
 *  - <b>Parameters:</b>
 *      -# <tt>[in,out]</tt> <b>p_dst</b> A pointer to the ring buffer where data will be appended.
 *      -# <tt>[in]</tt> <b>max_to_append</b> The upper limit in bytes that can be appended to the ring buffer.
 *      -# <tt>[out]</tt> <b>p_appended</b> A pointer to the number of bytes that were appended to the ring buffer.
 *  - <b>Returns:</b>
 *      -# <tt>True</tt> if successful.
 *
 * @param[in] reset_data_source A pointer to a function that resets the data source of the ring buffer in the event 
 * of an error. The function should not return anything and should not take any arguments. <tt>NULL</tt> indicates that
 * no routine should be performed on the data source when resetting the ring buffer.
 *
 * @param[in] enter_cirtical A pointer to a function that allows the routines to be interrupt or thread safe as desired.
 * The function should not return anything and should not take any arguments. <tt>NULL</tt> indicates that a no routine
 * is necessary for interrupt or thread safety.
 */
inline void ring_buffer_init(ring_buffer_t * p_ring, 
                             void * p_buffer, 
                             size_t buffer_size,
                             bool (*append)(void * p_destination, size_t max_to_append, size_t * p_appended),
                             void (*reset_data_source)(void),
                             void (*enter_critical)(void),
                             void (*exit_critical)(void))
{
    p_ring->p_buffer = p_buffer;
    p_ring->p_start = p_buffer;
    p_ring->size = buffer_size;
    p_ring->c_writes = 0;
    p_ring->c_reads = 0;
    p_ring->append = append;
    p_ring->reset_data_source = reset_data_source;
    p_ring->enter_critical = enter_critical;
    p_ring->exit_critical = exit_critical;
    
    return;
}

/**
 * Appends elements to the ring buffer.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 *
 * @param[out] p_appended A pointer to the number of bytes that were appended to the ring buffer.
 *
 * @return <tt>True</tt> if any data was successfully appended to the buffer.
 */
bool ring_buffer_append(ring_buffer_t * p_ring, size_t * p_appended)
{
    bool f_was_success = false;
    *p_appended = 0;
    
    ring_buffer_enter_critical(p_ring);
    size_t max_bytes_to_append = ring_buffer_get_free_size(p_ring);
    size_t fill_count = ring_buffer_get_fill_count(p_ring);
    
    if ((0 != max_bytes_to_append) &&
        (NULL != p_ring->append))
    {
        f_was_success = p_ring->append((uint8_t *)p_ring->p_start + fill_count, 
                                       max_bytes_to_append, 
                                       p_appended);
        ring_buffer_update_write_count(p_ring, *p_appended);
    }
    ring_buffer_exit_critical(p_ring);
    
    return f_was_success;
}

/**
 * Removes elements from the ring buffer and places them in a buffer or deletes them as desired.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 *
 * @param[out] p_destination A pointer to the buffer that will store the bytes removed from the ring buffer. If 
 * the pointer is <tt>NULL</tt> the items will simply be deleted.
 *
 * @param[in] size The number of bytes to be removed from the ring buffer.
 *
 * @param[out] p_size_removed A pointer to the number of bytes actually removed from the ring buffer.
 *
 * @return <tt>True</tt> if the total number of bytes specified were removed successfully.
 */
bool ring_buffer_remove(ring_buffer_t * p_ring, 
                        void * p_destination, 
                        size_t size, 
                        size_t * p_size_removed)
{
    bool f_was_success = false;
    *p_size_removed = 0;
    
    ring_buffer_enter_critical(p_ring);
    f_was_success = ring_buffer_copy(p_ring,
                                     0, // Always remove from the start of the ring buffer.
                                     p_destination, 
                                     size, 
                                     p_size_removed);
    ring_buffer_update_start(p_ring, *p_size_removed);
    ring_buffer_update_read_count(p_ring, *p_size_removed);
    ring_buffer_exit_critical(p_ring);
    
    return f_was_success;
}

/**
 * Peeks at the data in the ring buffer without removing it.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 *
 * @param[in] offset The byte offset into the ring buffer indicating where you would like to begin peeking.
 *
 * @param[out] p_destination A pointer to a buffer to store the bytes peeked at from within the ring buffer.
 *
 * @param[in] size The number of bytes to peek at from within the ring buffer.
 *
 * @param[out] p_size_peeked A pointer to the number of bytes actually peeked at from within the ring buffer.
 *
 * @return <tt>True</tt> if the total number of bytes specified were peeked at successfully.
 */
bool ring_buffer_peek(const ring_buffer_t * p_ring, 
                          size_t offset, 
                          void * p_destination, 
                          size_t size,
                          size_t * p_size_peeked)
{
    bool f_was_success = false;
    *p_size_peeked = 0;
    
    ring_buffer_exit_critical(p_ring);
    f_was_success = ring_buffer_copy(p_ring,
                                     offset,
                                     p_destination, 
                                     size, 
                                     p_size_peeked);
    ring_buffer_exit_critical(p_ring);
    
    return f_was_success;
}

/**
 * Copies data from the ring buffer.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 *
 * @param[in] offset The byte offset into the ring buffer.
 *
 * @param[out] p_destination A pointer to a buffer to store the bytes peeked at from within the ring buffer.
 *
 * @param[in] size The number of bytes to copy from the ring buffer.
 *
 * @param[out] p_size_copied A pointer to the number of bytes actually copied from the ring buffer.
 *
 * @return <tt>True</tt> if the total number of bytes specified were copied successfully.
 */
bool ring_buffer_copy(const ring_buffer_t * p_ring,
                          size_t offset,
                          void * p_destination,
                          size_t size,
                          size_t * p_size_copied)
{
    bool f_was_success = false;
    *p_size_copied = 0;
    
    if (NULL != p_destination)
    {
        // Copy bytes only up to the limit that they are available in the buffer from the offset given.
        *p_size_copied = ring_buffer_get_fill_count(p_ring) - offset;
        if (size <= *p_size_copied)
        {
            *p_size_copied = size;
            f_was_success = true;
        }
        
        uint8_t * p_buffer_end = ring_buffer_get_end(p_ring);
        uint8_t * p_start = (uint8_t *)p_ring->p_start + offset;
        size_t start_to_end_size = p_buffer_end - p_start + 1; 
        
        if (*p_size_copied <= start_to_end_size)
        {
            memcpy(p_destination, p_start, size);
        }
        else
        {
            memcpy(p_destination, p_start, start_to_end_size);
            memcpy(((uint8_t *)p_destination) + start_to_end_size,
                   p_ring->p_buffer,
                   (size - start_to_end_size));
        }
    }
    
    return f_was_success;
}

/**
 * Resets the ring buffer to its default state.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 */
void ring_buffer_reset(ring_buffer_t * p_ring)
{
    ring_buffer_enter_critical(p_ring);
    p_ring->p_start = p_ring->p_buffer;
    p_ring->c_writes = 0;
    p_ring->c_reads = 0;
    if (NULL != p_ring->reset_data_source)
    {
        p_ring->reset_data_source();
    }
    ring_buffer_exit_critical(p_ring);
    
    return;
}

/**
 * Gets the amount of free bytes remaining in the ring buffer.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 *
 * @return The amount of free bytes remaining in the ring buffer.
 */
inline size_t ring_buffer_get_free_size(const ring_buffer_t * p_ring)
{
    size_t free_size = p_ring->size - ring_buffer_get_fill_count(p_ring);
    
    return free_size;
}

/**
 * Gets the fill count of the ring buffer.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 *
 * @return The amount of bytes present in the ring buffer.
 */
inline size_t ring_buffer_get_fill_count(const ring_buffer_t * p_ring)
{
    size_t fill_count = (size_t)(p_ring->c_writes - p_ring->c_reads);
    
    return fill_count;
}

/**
 * Updates the write count for the ring buffer.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 *
 * @param[in] size The number of bytes that have been written to the ring buffer.
 */
inline void ring_buffer_update_write_count(ring_buffer_t * p_ring, size_t size)
{
    p_ring->c_writes += size; // Wrapping is intentional.
    
    return;
}

/**
 * Updates the read count for the ring buffer.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 *
 * @param[in] size The number of bytes that have been read from the ring buffer.
 */
inline void ring_buffer_update_read_count(ring_buffer_t * p_ring, size_t size)
{
    p_ring->c_reads += size; // Wrapping is intentional.
    
    return;
}

/**
 * Updates the start location for the ring buffer.
 *
 * @param[in,out] p_ring A pointer to a ring buffer instance.
 *
 * @param[in] The number of bytes to move the start location.
 */
void ring_buffer_update_start(ring_buffer_t * p_ring, size_t size)
{
    uint8_t * p_buffer_end = ring_buffer_get_end(p_ring);
    uint8_t * p_wrap_limit = p_buffer_end - size;
    
    if(((uint8_t *)p_ring->p_start) <= p_wrap_limit)
    {
        p_ring->p_start = (uint8_t *)p_ring->p_start + size;
    }
    else 
    {
        size_t distance_from_beginning = p_buffer_end - (uint8_t *)p_ring->p_start - 1;
        p_ring->p_start = (uint8_t *)p_ring->p_buffer + distance_from_beginning;
    }

    return;
}

/**
 * Gets the ring buffer's end.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 *
 * @return A pointer to the ring buffer's end.
 */
inline uint8_t * ring_buffer_get_end(const ring_buffer_t * p_ring)
{
    uint8_t * p_buffer_end = (uint8_t *)p_ring->p_buffer + p_ring->size - 1;
    
    return p_buffer_end;
}

/**
 * Enters a critical state to make the ring buffer opertions interrupt or thread safe.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 */
inline void ring_buffer_enter_critical(const ring_buffer_t * p_ring)
{
    if (NULL != p_ring->enter_critical)
    {
        p_ring->enter_critical();
    }
    
    return;
}

/**
 * Exits a critical state that makes the ring buffer opertions interrupt or thread safe.
 *
 * @param[in] p_ring A pointer to a ring buffer instance.
 */
inline void ring_buffer_exit_critical(const ring_buffer_t * p_ring)
{
    if (NULL != p_ring->exit_critical)
    {
        p_ring->exit_critical();
    }
    
    return;
}

